/**
 * 开源版本请务必保留此注释头信息，若删除捷码开源〔GEMOS〕官方保留所有法律责任追究！
 * 本软件受国家版权局知识产权以及国家计算机软件著作权保护（登记号：2018SR503328）
 * 不得恶意分享产品源代码、二次转售等，违者必究。
 * Copyright (c) 2020 gemframework all rights reserved.
 * http://www.gemframework.com
 * 版权所有，侵权必究！
 */
package com.gemframework.common.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.gemframework.common.exception.GemException;
import com.gemframework.common.utils.GemHTMLUtils;
import com.gemframework.model.entity.po.Datasource;
import com.gemframework.model.enums.ExceptionCode;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;


/**
 * @Title: DynamicDataSource
 * @Package: com.gemframework.common.config.druid
 * @Date: 2020-06-29 21:04:01
 * @Version: v1.0
 * @Description: 动态数据源核心类
 * @Author: nine QQ 769990999
 * @Copyright: Copyright (c) 2020 wanyong
 * @Company: www.gemframework.com
 */
@Data
public class DynamicDataSource extends AbstractRoutingDataSource {
    private boolean debug = true;
    private final Logger log = LoggerFactory.getLogger(getClass());
    private Map<Object, Object> dynamicTargetDataSources;
    private Object dynamicDefaultTargetDataSource;

    /**
     * 设置默认目标数据源
     * @Auth qq769990999
     * @param defaultTargetDataSource
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
    }


    /**
     * 设置动态目标数据源
     * @Auth qq769990999
     * @param targetDataSources
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        this.dynamicTargetDataSources = targetDataSources;
    }


    /**
     * 支配当前线程数据源
     * 实现此方法，由此方法的返回值决定具体从哪个数据源中获取连接
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String datasource = DBContextHolder.getDataSource();
        if (!StringUtils.isEmpty(datasource)) {
            Map<Object, Object> targetDsMap = this.dynamicTargetDataSources;
            if (targetDsMap.containsKey(datasource)) {
                log.debug("---当前数据源：" + datasource + "---");
            } else {
                log.debug("不存在的数据源：" + datasource);
                return null;
            }
        } else {
            log.debug("---当前数据源：默认数据源---");
        }
        return datasource;
    }


    /**
     * 创建数据源
     * @Auth qq769990999
     * @param key
     * @param driveClass
     * @param url
     * @param username
     * @param password
     * @return
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    public boolean createDataSource(String key, String driveClass, String url, String username, String password) {
        try {
            try { // 排除连接不上的错误
                Class.forName(driveClass);
                DriverManager.getConnection(GemHTMLUtils.filterXssString(url), username, password);// 相当于连接数据库
            } catch (Exception e) {
                log.error("连接异常："+e.getMessage());
                e.printStackTrace();
                return false;
            }
            @SuppressWarnings("resource")
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setName(key);
            druidDataSource.setDriverClassName(driveClass);
            druidDataSource.setUrl(GemHTMLUtils.filterXssString(url));
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setInitialSize(1); //初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时
            druidDataSource.setMaxActive(20); //最大连接池数量
            druidDataSource.setMaxWait(60000); //获取连接时最大等待时间，单位毫秒。当链接数已经达到了最大链接数的时候，应用如果还要获取链接就会出现等待的现象，等待链接释放并回到链接池，如果等待的时间过长就应该踢掉这个等待，不然应用很可能出现雪崩现象
            druidDataSource.setMinIdle(5); //最小连接池数量
            String validationQuery = "select 1 from dual";
            druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效，这里建议配置为TRUE，防止取到的连接不可用
            druidDataSource.setTestWhileIdle(true);//建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。
            druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql，要求是一个查询语句。如果validationQuery为null，testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            druidDataSource.setFilters("stat");//属性类型是字符串，通过别名的方式配置扩展插件，常用的插件有：监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间，单位是毫秒，这里配置为3分钟180000
            druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后，当连接池空闲时，池中的minIdle数量以内的连接，空闲时间超过minEvictableIdleTimeMillis，则会执行keepAlive操作，即执行druid.validationQuery指定的查询SQL，一般为select * from dual，只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间，就可以保证当连接空闲时自动做保活检测，不会被防火墙切断
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间)；单位为秒。这里配置为1小时
            druidDataSource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
            druidDataSource.init();
            this.dynamicTargetDataSources.put(key, druidDataSource);
            setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
            super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
            log.debug(key+"数据源初始化成功");
            log.debug(key+"数据源的概况："+druidDataSource.dump());
            return true;
        } catch (Exception e) {
            log.error(e + "");
            return false;
        }
    }

    /***
     * 创建数据源
     * @param dataSource
     * @throws Exception
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    private boolean createDataSource(Datasource dataSource) {
        String dsName = dataSource.getName();
        log.info("准备创建数据源"+dsName);
        String username = dataSource.getUsername();
        String password = dataSource.getPassword();
        String url = dataSource.getUrl();
        String driveClass = dataSource.getDriverClassName();
        if(testDatasource(driveClass,url,username,password)) {
            boolean result = this.createDataSource(dsName, driveClass, url, username, password);
            if(!result) {
                log.error("数据源"+dsName+"配置正确，但是创建失败");
                return false;
            }
        } else {
            log.error("数据源配置有错误");
            return false;
        }
        return true;
    }


    /**
     * 测试连接
     * @Auth qq769990999
     * @param driveClass
     * @param url
     * @param username
     * @param password
     * @return
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    public boolean testDatasource(String driveClass, String url, String username, String password) {
        Connection connection;
        try {
            Class.forName(driveClass);
            connection = DriverManager.getConnection(GemHTMLUtils.filterXssString(url), username, password);
            if(connection == null ){
                return false;
            }else {
                connection.close();
            }
            return true;
        } catch (Exception e) {
            log.error("连接失败："+e.getMessage());
            return false;
//            throw new GemException(ExceptionCode.DATASOURCE_CONNECT_FAIL);
        }
    }




    /**
     * 创建并检测数据源
     * @param dataSource
     * @throws Exception
     * @Copyright: Copyright (c) 2020 gemframework.com
     */
    public boolean createDataSourceWithCheck(Datasource dataSource){
        String dsName = dataSource.getName();
        log.debug("正在检查数据源："+dsName);
        Map<Object, Object> targetDsMap = this.dynamicTargetDataSources;
        if (targetDsMap.containsKey(dsName)) {
            log.debug("数据源"+dsName+"之前已经创建，准备测试数据源是否正常...");
            DruidDataSource druidDataSource = (DruidDataSource) targetDsMap.get(dsName);
            boolean rightFlag = true;
            Connection connection = null;
            try {
                log.debug(dsName+"数据源的概况->当前闲置连接数："+druidDataSource.getPoolingCount());
                long activeCount = druidDataSource.getActiveCount();
                log.debug(dsName+"数据源的概况->当前活动连接数："+activeCount);
                if(activeCount > 0) {
                    log.debug(dsName+"数据源的概况->活跃连接堆栈信息："+druidDataSource.getActiveConnectionStackTrace());
                }
                log.debug("准备获取数据库连接...");
                connection = druidDataSource.getConnection();
                log.info("数据源"+dsName+"正常");
            } catch (Exception e) {
                log.error(e.getMessage(),e); //把异常信息打印到日志文件
                rightFlag = false;
                log.error("缓存数据源"+dsName+"已失效，准备删除...");
                if(delDatasources(dsName)) {
                    log.error("缓存数据源删除成功");
                } else {
                    log.error("缓存数据源删除失败");
                }
            } finally {
                if(null != connection) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        log.info("关闭异常："+e.getMessage());
                        e.printStackTrace();
                    }
                }
            }
            if(rightFlag) {
                log.info("不需要重新创建数据源");
                return true;
            } else {
                log.info("准备重新创建数据源...");
                boolean ret = createDataSource(dataSource);
                log.info("重新创建数据源完成");
                return ret;
            }
        } else {
            return createDataSource(dataSource);
        }
    }

    /**
     * 删除数据源
     * @Copyright: Copyright (c) 2020 gemframework.com
     * @param dsName 数据源名称
     * @return
     */
    public boolean delDatasources(String dsName) {
        log.info("删除数据源：{}",dsName);
        Map<Object, Object> targetDsMap = this.dynamicTargetDataSources;
        if (targetDsMap.containsKey(dsName)) {
            Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
            for (DruidDataSource dataSource : druidDataSourceInstances) {
                if (dsName.equals(dataSource.getName())) {
                    targetDsMap.remove(dsName);
                    DruidDataSourceStatManager.removeDataSource(dataSource);
                    setTargetDataSources(targetDsMap);// 将map赋值给父类的TargetDataSources
                    super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }
}
